package leader

import (
	"context"
	"errors"
	"fmt"
	"github.com/go-logr/logr"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/opa"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/creation"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/lease"
	initManager "gitverse.ru/synapse/kubelatte/pkg/operator/controllers/manager"
	"gitverse.ru/synapse/kubelatte/pkg/storage"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kclientset "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/leaderelection"
	"k8s.io/client-go/tools/leaderelection/resourcelock"
	"net/http"
	"os"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/controller-runtime/pkg/webhook"
	"strings"
	"time"
)

var Mgr manager.Manager

var (
	initMgr           initManager.InitManagerInterface
	templateR         controllers.ReconcilerInterface
	triggerR          controllers.ReconcilerInterface
	scopeR            controllers.ReconcilerInterface
	namespaceR        controllers.ReconcilerInterface
	storageController *storage.StorageController
)

func start() {
	controllers.IsInit.Mu.Lock()

	if controllers.IsInit.IsInit {
		logs.Debug("Already init")
		return
	}

	controllers.InitEnv()

	ctx := ctrl.SetupSignalHandler()

	{
		initCtx, cancelInitCtx := context.WithCancel(context.Background())
		defer cancelInitCtx()

		initMgr = initManager.GetInitManager()
		go initMgr.Start(initCtx)
	}

	{
		logs.Debugf("[KBLT][Operator] STORAGE started")
		startStorage()
	}

	if controllers.IsControllerNeeded("scope") {
		logs.Info("[KBLT][Operator] Controller SCOPE started")
		startScopeController()
	}

	if controllers.IsControllerNeeded("trigger") {
		logs.Info("[KBLT][Operator] Controller TRIGGER started")
		startTriggerController()
	}

	if controllers.IsControllerNeeded("template") {
		logs.Info("[KBLT][Operator] Controller TEMPLATE started")
		startTemplateController()
	}

	if controllers.IsControllerNeeded("triggerInstance") {
		logs.Info("[KBLT][Operator] Controller TRIGGER_INSTANCE started")
		go startTriggerInstanceController(ctx)
	}

	logs.Info("[KBLT][Operator] Controller NAMESPACE started")
	go startNamespaceController(ctx)

	controllers.IsInit.IsInit = true
	controllers.IsInit.Mu.Unlock()

	ctrl.SetLogger(logr.Logger{}.WithSink(log.NullLogSink{}))

	//+kubebuilder:scaffold:builder
	if err := Mgr.AddHealthzCheck("healthz", healthz); err != nil {
		logs.Fatal(fmt.Sprintf("Unable to set up health check: %s", err.Error()))
		os.Exit(1)
	}
	if err := Mgr.AddReadyzCheck("readyz", readyz); err != nil {
		logs.Fatal(fmt.Sprintf("Unable to set up ready check: %s", err.Error()))
		os.Exit(1)
	}

	StartLoggerServer(ctx)

	if err := Mgr.Start(ctx); err != nil {
		logs.Fatal(fmt.Sprintf("Problem running manager: %s", err.Error()))
		os.Exit(1)
	}
}

func healthz(_ *http.Request) error {
	if initMgr.DoesTemplatesExist() && templateR != nil && !templateR.HealthCheck() {
		return errors.New("TemplateController is not initialized")
	}
	if initMgr.DoesTriggersExist() && triggerR != nil && !triggerR.HealthCheck() {
		return errors.New("TriggerController is not initialized")
	}
	if initMgr.DoesScopesExist() && scopeR != nil && !scopeR.HealthCheck() {
		return errors.New("ScopeController is not initialized")
	}
	return nil
}

func readyz(_ *http.Request) error {
	if initManager.PermissionsSet == false {
		return errors.New("dont have all required permissions")
	}
	return nil
}

func StartLoggerServer(ctx context.Context) {
	addr := fmt.Sprintf(":%d", util.IKbltLoggerPort)
	router := http.NewServeMux()
	logs.ChangeDebugLevelServerStart(addr, router)
	go func() {
		if err := http.ListenAndServe(addr, router); err != nil {
			logs.Error(fmt.Sprintf("Error start debug level server: %s", err.Error()))
			os.Exit(1)
		}
		<-ctx.Done()
		logs.Debugf("Stop logger server")
		os.Exit(0)
	}()
}

func startTriggerController() {
	triggerR = &controllers.TriggerReconciler{
		Client: Mgr.GetClient(),
		Scheme: Mgr.GetScheme(),
	}

	if err := triggerR.SetupWithManager(Mgr); err != nil {
		logs.Fatal(fmt.Sprintf("Unable to create Trigger controller: %s", err.Error()))
		os.Exit(1)
	}
}

func startTemplateController() {
	templateR = &controllers.TemplateReconciler{
		Client: Mgr.GetClient(),
		Scheme: Mgr.GetScheme(),
	}

	if err := templateR.SetupWithManager(Mgr); err != nil {
		logs.Fatal(fmt.Sprintf("Unable to create Template controller: %s", err.Error()))
		os.Exit(1)
	}
}

func startScopeController() {
	scopeR = &controllers.ScopeReconciler{
		Client: Mgr.GetClient(),
		Scheme: Mgr.GetScheme(),
	}

	if err := scopeR.SetupWithManager(Mgr); err != nil {
		logs.Fatal(fmt.Sprintf("Unable to create Scope controller: %s", err.Error()))
		os.Exit(1)
	}
}

func startStorage() {
	// [S1] Must start before storage
	storageController = &storage.StorageController{Sync: true}
	storage.Storage = storageController

	opa.OpaController.Start(false)
	// [S1] end

	storageController.Start(true, false)
}

func startNamespaceController(ctx context.Context) {
	opt := ctrl.Options{
		Logger:             Mgr.GetLogger(),
		Scheme:             Mgr.GetScheme(),
		WebhookServer:      webhook.NewServer(webhook.Options{Port: 9444}),
		MetricsBindAddress: "0",
	}

	clusterMgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opt)
	if err != nil {
		logs.Fatal(fmt.Sprintf("Problem running namespace manager: %s", err.Error()))
		os.Exit(1)
	}

	namespaceR = &controllers.NamespaceReconciler{
		Client: clusterMgr.GetClient(),
		Scheme: clusterMgr.GetScheme(),
	}

	if err := namespaceR.SetupWithManager(clusterMgr); err != nil {
		logs.Fatal(fmt.Sprintf("Unable to create Namespace controller: %s", err.Error()))
		os.Exit(1)
	}

	go func() {
		if err := clusterMgr.Start(ctx); err != nil {
			logs.Fatal(fmt.Sprintf("Problem running namespace manager: %s", err.Error()))
			os.Exit(1)
		}
		logs.Debugf("Starting cluster namespace manager")
	}()
}

func startTriggerInstanceController(ctx context.Context) {
	time.Sleep(time.Second * 5) // hack for order controller run, must refactor

	var renderTemplate = &creation.RenderController{}
	renderTemplate.Start()
	creation.RC = renderTemplate

	var watcherController = &creation.WatcherController{ReSyncPeriod: 5}
	watcherController.Start()

	var factoryController = &creation.FactoryController{}
	factoryController.Start()

	if strings.EqualFold(controllers.CreationMode, "cluster") {
		opt := ctrl.Options{
			Logger:             Mgr.GetLogger(),
			Scheme:             Mgr.GetScheme(),
			WebhookServer:      webhook.NewServer(webhook.Options{Port: 9442}),
			MetricsBindAddress: "0",
		}

		clusterMgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opt)
		if err != nil {
			logs.Fatal(fmt.Sprintf("Problem running ti manager: %s", err.Error()))
			os.Exit(1)
		}
		var triggerInstanceController controllers.ReconcilerInterface = &controllers.TriggerInstanceReconciler{
			Client: clusterMgr.GetClient(),
			Scheme: clusterMgr.GetScheme(),
		}

		if err := triggerInstanceController.SetupWithManager(clusterMgr); err != nil {
			logs.Fatal(fmt.Sprintf("Unable to create TriggerInstance controller: %s", err.Error()))
			os.Exit(1)
		}

		go func() {
			if err := clusterMgr.Start(ctx); err != nil {
				logs.Fatal(fmt.Sprintf("Problem running ti manager: %s", err.Error()))
				os.Exit(1)
			}
			logs.Debugf("Starting cluster ti manager")
		}()
	} else {
		var triggerInstanceController controllers.ReconcilerInterface = &controllers.TriggerInstanceReconciler{
			Client: Mgr.GetClient(),
			Scheme: Mgr.GetScheme(),
		}

		if err := triggerInstanceController.SetupWithManager(Mgr); err != nil {
			logs.Fatal(fmt.Sprintf("Unable to create TriggerInstance controller: %s", err.Error()))
			os.Exit(1)
		}
	}
}

// todo: move to lease
func runLeaderElection(lock *resourcelock.LeaseLock, ctx context.Context, id string) {
	leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{
		Lock:            lock,
		ReleaseOnCancel: true,
		LeaseDuration:   15 * time.Second,
		RenewDeadline:   10 * time.Second,
		RetryPeriod:     2 * time.Second,
		Callbacks: leaderelection.LeaderCallbacks{
			OnStartedLeading: func(c context.Context) {
				logs.Infof("Leader %s started", lock.LockConfig.Identity)
				lease.SetMeLeader()
				start()
				if controllers.IsCreatorRole() && controllers.IsInit.IsInit {
					logs.Debugf("Start checking unhandled TIs")
					storage.CheckUnhandledTIs()
				}
			},
			OnStoppedLeading: func() {
				logs.Warn(lock.LockConfig.Identity, "no longer the leader")
				lease.SetMeSlave()
			},
			OnNewLeader: func(currentId string) {
				if currentId == id {
					return
				}
				logs.Infof("Current leader is %s", currentId)
				lease.SetMeSlave()
				start()
			},
		},
	})
}

func getNewLock(lockname, podname, namespace string, cl *kclientset.Clientset) *resourcelock.LeaseLock {
	return &resourcelock.LeaseLock{
		LeaseMeta: metav1.ObjectMeta{
			Name:      lockname,
			Namespace: namespace,
		},
		Client: cl.CoordinationV1(),
		LockConfig: resourcelock.ResourceLockConfig{
			Identity: podname,
		},
	}
}

func SetLeaderAndStart(leaseLockName, podName, leaseLockNamespace string) {
	client := kclientset.NewForConfigOrDie(Mgr.GetConfig())
	lock := getNewLock(leaseLockName, podName, leaseLockNamespace, client)
	runLeaderElection(lock, context.TODO(), podName)
}
